package aorm

import (
	"errors"
	"fmt"
	"gitee.com/KotlinToGo/aorm/dialect"
	"gitee.com/KotlinToGo/sqlx"
)

// 集群
type cluster struct {
	master     []*sqlx.DB
	slave      []*sqlx.DB
	masterSize int
	slaveSize  int
}

// Config 单点配置
type Config struct {
	Driver string `json:"driver"` // 驱动: mysql/sqlite3/oracle/mssql/postgres/clickhouse, 如果集群配置了驱动, 这里可以省略
	// mysql 示例:
	// root:root@tcp(localhost:3306)/test?charset=utf8mb4&parseTime=true
	Dsn            string `json:"dsn"`            // 数据库链接
	SetMaxOpenConn int    `json:"setMaxOpenConn"` // (连接池)最大打开的连接数，默认值为0表示不限制
	SetMaxIdleConn int    `json:"setMaxIdleConn"` // (连接池)闲置的连接数, 默认0
	Prefix         string `json:"prefix"`         // 表前缀, 如果集群配置了前缀, 这里可以省略
}

// ConfigCluster 集群配置
type ConfigCluster struct {
	Master []Config // 主数据库配置节点
	Slave  []Config // 从数据库配置节点
	Driver string   // 驱动
	Prefix string   // 前缀
}

// Engine 全局配置	(初始化配置模块, 可以全局保存并复用。)
type Engine struct {
	config  *ConfigCluster
	driver  string
	prefix  string
	dbs     *cluster
	logger  ILogger
	dialect dialect.Dialect //SQL数据库方言
	s *Session
}

// New : 初始化 Engine 结构体对象指针
func New (config ...interface{}) (e *Engine, err error) {
	engine := new(Engine)
	if len(config) == 0 {
		return nil,errors.New(" config不能为空!")
	}

	// 使用默认的log, 如果自定义了logger, 则只需要调用 Use() 方法即可覆盖
	engine.Use(DefaultLogger())

	switch config[0].(type) {

	// 传入的是单个配置
	case *Config:
		err = engine.bootSingle(config[0].(*Config))
		fmt.Println("参数<config>的类型是 *Config")
	case Config:
		err = engine.bootSingle(config[0].(*Config))
		fmt.Println("参数<config>的类型是 Config")

		// 传入的是集群配置
	case *ConfigCluster:
		engine.config = config[0].(*ConfigCluster)
		fmt.Println("参数<config>的类型是 *ConfigCluster")
		err = engine.bootCluster()
	case ConfigCluster:
		engine.config = config[0].(*ConfigCluster)
		err = engine.bootCluster()
		fmt.Println("参数<config>的类型是 ConfigCluster")
	default:
		panic(fmt.Sprintf(" 您的输入参数%v有误！请输入&aorm.Config{}，或者&aorm.ConfigCluster{} ",config))

	}
	// 初始化 Session 模块
	engine.s = initSession(engine)
	return engine, err
}

// bootReal 根据单点配置获取一个 sqlx.DB 句柄
func (c *Engine) bootReal(dbConf Config) (db *sqlx.DB, err error) {

	db, err = sqlx.Connect(dbConf.Driver, dbConf.Dsn)
	if err != nil {
		return
	}

	// 连接池设置
	if dbConf.SetMaxOpenConn > 0 {
		db.SetMaxOpenConns(dbConf.SetMaxOpenConn)
	}
	if dbConf.SetMaxIdleConn > 0 {
		db.SetMaxIdleConns(dbConf.SetMaxIdleConn)
	}
	return
}

// bootCluster 设置集群 sqlx.DB
func (c *Engine) bootCluster() error {

	if len(c.config.Slave) > 0 {
		for _, item := range c.config.Slave { // item是每一个slave配置节点
			if c.config.Driver != "" {
				item.Driver = c.config.Driver // 每一个slave从库的驱动 == 集群配置的驱动
			}

			if c.config.Prefix != "" {
				item.Prefix = c.config.Prefix // 每一个slave从库的表前缀 == 集群配置的表前缀
			}
			db, err := c.bootReal(item) // 获取每一个 slave配置节点的 sqlx.DB句柄
			if err != nil {
				return err
			}
			if c.dbs == nil {
				c.dbs = new(cluster)
			}
			c.dbs.slave = append(c.dbs.slave, db) // 将 slave的 sqlx.DB句柄放入对应的 slave集群中。
			c.dbs.slaveSize++
			c.driver = item.Driver
		}
	}

	var pre, dr string
	if len(c.config.Master) > 0 {
		for _, item := range c.config.Master {
			if c.config.Driver != "" {
				item.Driver = c.config.Driver
			}
			if c.config.Prefix != "" {
				item.Prefix = c.config.Prefix
			}
			db, err := c.bootReal(item)

			if err != nil {
				return err
			}
			if c.dbs == nil {
				c.dbs = new(cluster)
			}
			c.dbs.master = append(c.dbs.master, db)
			c.dbs.masterSize = c.dbs.masterSize + 1
			c.driver = item.Driver

			if item.Prefix != "" {
				pre = item.Prefix //master config节点的前缀
			}
			if item.Driver != "" {
				dr = item.Driver //master config节点的驱动
			}
		}
	}
	// 如果 Engine 没有设置prefix,且 configCluster 设置了prefix,则使用cluster的prefix
	if pre != "" && c.prefix == "" {
		c.prefix = pre
	}
	// 如果 Engine 没有设置driver,且configCluster设置了driver,则使用cluster的driver
	if dr != "" && c.driver == "" {
		c.driver = dr
	}
	return nil
}


// bootSingle 如果传入的是单一配置, 则转换成集群配置, 方便统一管理
func (c *Engine) bootSingle(config *Config) error {
	// 如果传入的是单一配置, 则转换成集群配置, 方便统一管理
	var cc = new(ConfigCluster)
	cc.Master = append(cc.Master, *config)
	c.config = cc
	return c.bootCluster()
}

// GetPrefix 获取前缀。
func (c *Engine) GetPrefix() string {
	return c.prefix
}

// GetDriver 获取驱动。
func (c *Engine) GetDriver() string {
	return c.driver
}

// GetLogger 获取一个logger实例。
func (c *Engine) GetLogger() ILogger {
	return c.logger
}

// GetMasterDB : 获取一个主库用来做查询之外的操作
func (c *Engine) GetMasterDB() *sqlx.DB {
	if c.dbs.masterSize == 0 {
		return nil
	}
	var randInt = getRandomInt(c.dbs.masterSize)
	return c.dbs.master[randInt]
}

// GetSlaveDB : 获取一个从库用来做查询操作
func (c *Engine) GetSlaveDB() *sqlx.DB {
	if c.dbs.slaveSize == 0 {
		return c.GetMasterDB()
	}
	var randInt = getRandomInt(c.dbs.slaveSize)
	return c.dbs.slave[randInt]
}

// SetTagKey 自定义结构体对应的orm字段,默认orm
func (c *Engine) SetTagKey(arg string) {
	//c.tagName = arg
	TagKey = arg
}

// SetIgnoreName 自定义结构体对应的orm忽略字段名字,默认-
func (c *Engine) SetIgnoreName(arg string) {
	Ignore = arg
}

// SetPrefix 设置表前缀
func (c *Engine) SetPrefix(prefix string) {
	c.prefix = prefix
}

// SetLogger ...
func (c *Engine) SetLogger(logger ILogger) {
	c.logger = logger
}

// Ping ...
func (c *Engine) Ping() error {
	return c.GetSlaveDB().Ping()
}

// Use 使用自定义插件，可以修改 Engine 的配置。
func (c *Engine) Use(closers ...func(e *Engine)) {
	for _, closer := range closers {
		closer(c)
	}
}

// NewSession 获取 Session 实例
// 这是一个语法糖, 为了方便使用(engine.NewSession())添加的
// 添加后会让 Engine 和 Session 耦合，便于 Engine 管理 Session 模块。
//func (c *Engine) NewSession() ISession {
//	return newSession(c)
//}
